# Vital bits of bashy2

# This file is part of bashy2, an irc bot used on irc.sdf.org.

# Copyright (C) 2015-2016 mlaine@sdfeu.org, grugly@sdf.org
# Copyright (C) 2017-2018 mlaine@sdfeu.org

# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:

# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#-----------------------------------------------------------------------

function debug {
    # Sends data to #bashy2-debug.
    if [ "${debug}" = 0 ]; then
        printf 'PRIVMSG #bashy2-debug :%s\n' "$*" >&3
    fi

    ## Writes debug data to ~/bashy.log
    if [ "${debug}" = "logfile" ]; then
        echo "$*" >> ${HOME}/bashy.log
    fi
}

function nonvital_join {
    # A parent function for nonvital function calls. Called for every JOIN.
    welcome
}

function nonvital_pm {
    # A parent function for nonvital function calls. Called for every PRIVMSG.
    seen
}

function parse {
    # Parses PRIVMSG formatted information.
    local x="${1%\!*}"
    local y="${1%@*}"
    nick="${x:1}"
    user="${y#*\!}"
    host="${1#*@}"
    chan="$3"
    msg="${4:1}"
    local z="${msg:1}"
    cmd="${z,,}"
    args=("${@:5}")
    prefix="${msg:0:1}"
}

function multiply {
    # "$(multiply 3 'foo')" -> 'foo foo foo'
    local x="$(printf "%"$1"s")"
    local y="${x// /"$2" }"
    printf '%s\n' "${y% *}"
}

function join {
    # Joins all channels.
    local chans=('./channels/'*)
    local chlist="${chans[*]##*/}"
    send 'join' "${chlist// /,}"
}

function chmode {
    # If called with no args, resets channel modes for all channels.
    # Otherwise lists all channels.
    local chans=('./channels/'*)
    if [[ "$1" ]]; then
        send 'pm' "Channels: ${chans[*]##*/}"
    else
        for channel in "${chans[@]##*/}"; do
            local mode="$(awk 'NR==2' "./channels/$channel")"
            local bans="$(awk 'NR==3' "./channels/$channel")"
            send 'mode' "$channel" "$mode"
            send 'mode' "$channel" "$bans"
        done
    fi
}

function chtopic {
    # If called with no args, queries the topic of every channel. A query
    # results main() calling chtopic() with args, resulting in the actual
    # setting of topics (should they differ from their default values).
    if (( ! "$#" )); then
        local chans=('./channels/'*)
        for channel in "${chans[@]##*/}"; do
            send 'qtopic' "$channel"
        done
    else
        local ch="$4"
        local foo="${*:5}"
        local topic="${foo:1}"
        local default="$(awk 'NR==1' "./channels/$ch")"
        if [[ -n "$default" && "$topic" != "$default" ]]; then
            send 'topic' "$ch" "$default"
        fi
    fi
}

function up {
    # Changes a user's mode based on the output of WHOIS received from
    # the server. WHOIS is issued by the user's request '!up'.
    if [[ "$2" = '311' ]]; then
        nick="$4"
        user="$5"
        host="$6"
        auth
    elif [[ -n "$flag" ]]; then
        local foo=("${@:5}")
        local chans=("${foo[@]//[:~&@%+]/}")
        local name="$(multiply "${#flag}" "$nick")"
        for channel in "${chans[@]}"; do
            if [[ -f "./channels/$channel" ]]; then
                send 'mode' "$channel" "+$flag" "$name"
            fi
        done
    fi
}

function upall {
    # Changes user modes based on the output of WHO received from the
    # server. WHO is issued by serv() 'upall'.
    if [[ "$2" = '315' ]]; then
        local ch="$4"
        sendup "$ch" 'o'
        sendup "$ch" 'h'
        sendup "$ch" 'v'
        ops=(); hops=(); voices=()
    else
        user="$5"
        host="$6"
        nick="$8"
        stats="$9"

        ## Test if sixth field of RPL_WHOREPLY indicates chan voice or higher,
        ## and shuck if true. This reduces unneeded shell commands and irc commands
        local reg_shuck="[~&@%+]"      ## See E14) of https://tiswww.case.edu/php/chet/bash/FAQ
        if [[ ! "${stats}" =~ ${reg_shuck} ]]; then
            auth
        fi

        case "$flag" in
            'ohv')
                ops+=("$nick")
                hops+=("$nick")
                voices+=("$nick") ;;
            'hv')
                hops+=("$nick")
                voices+=("$nick") ;;
            'v')
                voices+=("$nick") ;;
        esac
    fi
}

function sendup {
    # Simplifies upall().
    local ch="$1"
    case "$2" in
        'o') local nicks=("${ops[@]}") ;;
        'h') local nicks=("${hops[@]}") ;;
        'v') local nicks=("${voices[@]}") ;;
    esac
    local f="$(printf 'f%.0s' $(seq 1 "$modes"))"
    while (( "${#nicks[@]}" > "$modes" )); do
        send 'mode' "$ch" "+${f//f/$2}" "${nicks[*]:0:$modes}"
        local nicks=("${nicks[@]:$modes}")
        sleep 3     ## Helps combat hitting sendq limit
    done
    if (( "${#nicks[@]}" > 0 )); then
        local f="$(printf 'f%.0s' $(seq 1 "${#nicks[@]}"))"
        send 'mode' "$ch" "+${f//f/$2}" "${nicks[*]}"
        sleep 3     ## Helps combat hitting sendq limit
    fi
}

function send {
    # A wrapper function to simplify sending messages to the server.
    debug 'OUT:' "$@"
    case "$1" in
        'pong')
            printf 'PONG %s\n' "$2" >&3 ;;
        'pm')
            printf 'PRIVMSG %s :%s\n' "$chan" "$2" >&3 ;;
        'mode')
            printf 'MODE %s %s %s\n' "$2" "$3" "$4" >&3 ;;
        'join')
            printf 'JOIN %s\n' "$2" >&3 ;;
        'part')
            printf 'PART %s\n' "$2" >&3 ;;
        'oper')
            printf 'OPER %s %s\n' "$2" "$3" >&3 ;;
        'user')
            printf 'USER %s 0 * :%s\n' "$2" "$3" >&3 ;;
        'nick')
            printf 'NICK %s\n' "$2" >&3 ;;
        'topic')
            printf 'TOPIC %s :%s\n' "$2" "$3" >&3 ;;
        'qtopic')
            printf 'TOPIC %s\n' "$2" >&3 ;;
        'whois')
            printf 'WHOIS %s\n' "$2" >&3 ;;
        'who')
            printf 'WHO %s\n' "$2" >&3 ;;
        'quit')
            printf "QUIT :I'll be back!\n" >&3 ;;
    esac
}

function serv {
    # Maintainer only commands.
    case "$1" in
        'list')
            chmode 0 ;;
        'join')
            join
            sleep 5 && chmode &&
            sleep 5 && chtopic &&
            sleep 5 && serv 'upall' & ;;
        'delete')
            local ch="${args[0]}"
            if [[ -f "./channels/$ch" ]]; then
                rm "./channels/$ch"
                send 'pm' "Channel $ch deleted; remember to push your changes!"
                send 'part' "$ch"
            else
                send 'pm' "Channel ${ch:-null} does not exist!"
            fi ;;
        'chmode')
            chmode
            send 'pm' 'Channel modes set.' ;;
        'chtopic')
            chtopic
            send 'pm' 'Channel topics set.' ;;
        'upall')
            local chans=('./channels/'*)
            for channel in "${chans[@]##*/}"; do
                send 'who' "$channel"
            done ;;
        'topic')
            local ch="${args[0]}"
            local to="${args[*]:1}"
            if [[ -f "./channels/$ch" ]]; then
                printf '%s\n' 1c "$to" . w | ed -s "./channels/$ch"
                (( "$quiet" == 1 )) && chtopic
            else
                send 'pm' "Channel ${ch:-null} does not exist!"
            fi ;;
        'head')
            send 'pm' "$(git log -1 --pretty="format:%h: %s [%an, %ci]")" ;;
        'pull')
            ( git fetch origin master && git reset --hard FETCH_HEAD &&
              git clean -df ) 1>'/dev/null' &&
            send 'pm' 'Pulled!' ;;
        'reload')
            source './bits/conf.sh'
            source './bits/vitalbits.sh'
            source './bits/nonvitalbits.sh'
            send 'pm' 'Reloaded!' ;;
        'restart')
            ( sleep 5 && './bashy2' & )
            send 'pm' 'Restarting...'
            send 'quit'
            exit 0 ;;
        'debug')
            echo "DEBUG: debug toggle [${args[0]}]" >> $HOME/bashy.log
            if [ "${args[0]}" = 0 ]; then
                debug=0
                send 'pm' 'Debug log available at #bashy2-debug.'
            elif [ "${args[0]}" = "logfile" ]; then
                debug="logfile"
                send 'pm' 'Debug log available in ~/bashy.log'
            else
                debug=1
                send 'pm' 'Debug logging off.'
            fi ;;
        'quiet')
            if (( "$quiet" == 0 )); then
                quiet=1
                send 'pm' 'Volume up!'
            else
                quiet=0
                send 'pm' 'I will shut up.'
            fi ;;
        'die')
            send 'pm' "What'd I do?"
            send 'quit'
            exit 0 ;;
    esac
}

function run {
    # Runs commands.
    if [[ -x "$cmd_dir/$cmd" ]]; then
        ( cd "$cmd_dir" && "./$cmd" "$nick" "$chan" "${args[@]}" ) &
    else
        send 'pm' "'$cmd' not found. See !commands for available commands."
    fi
}

function priv {
    # Processes every PRIVMSG.
    parse "$@"
    if [[ "$chan" = "$bot_nick" ]]; then
        chan="$nick"
    else
        nonvital_pm
    fi
    2botpriv
    if [[ "$msg" = '!up' ]]; then
        send 'whois' "$nick"
        return 0
    elif [[ -n "$cmd" && ("$quiet" -eq 1 || "$chan" = "$nick") &&
        "$prefix" = '!' && ! "$cmd" =~ [^[:alnum:]_] ]]; then
        run
        return 0
    fi
    if [[ "$prefix" = '%' && -f "./maintainers/$nick" ]]; then
        auth; [[ -n "$flag" ]] && serv "$cmd"
    elif [[ "$chan" = "$nick" ]]; then
        send 'pm' "I am a bot. Try '!help' instead."
    fi
}

function user_join {
    # Processes every JOIN.
    parse "$@"
    nonvital_join
    2botjoin
    auth
    if [[ -n "$flag" ]]; then
        local ch="${chan:1}"
        local name="$(multiply "${#flag}" "$nick")"
        send 'mode' "$ch" "+$flag" "$name"
    fi
}

function user_quit {
    # Processes every QUIT.
    2botquit "${1%\!*}"
}

